package service

import (
	"encoding/json"
	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
	"guard/internal/app/oauth/models"
	"guard/internal/app/oauth/store"
	"guard/internal/errors"
	"guard/internal/tool"
	"strings"
)

/*
规定：
	service每个方法需要明确的参数，必须是由上层api，分解后得到的参数
	service必须写每个业务的接口，抽象到某个文件，并返回接口，和实现接口结构体的指针
*/

const refreshGrantType = "refresh_token"

/**
1.如果存储id到那个id，则每次client都需要查询，这样才能实现Consent功能，需要更改
2.consent功能实现待定
3.dao层err都在这里进行处理，注意处理err的参数关系，
	如：从数据库中或在程序中生成的参数，确定性参数，查询数据库则可以不处理err
		而请求来的，或传递过来的参数，不确定性的参数，则需要处理err
*/

// Authorization 授权
func (s *oauthServiceImpl) Authorization(username, clientIdReq, grantTypeReq, redirectUriReq, codeChallenge, codeChallengeMethod string, scopeReq []string) (client, state, scope, uri string, err error) {
	// 1.根据client_id,查询客户端信息
	registerId, clientId, _, _, grantTypes, redirectUri, scope, codeExp, _, _, err := s.db.SelClient(clientIdReq)
	// 1.1.查询到的client信息
	if err != nil {
		return "", "", "", "", errors.OtherError("client info is error")
	}

	// 1.1.2.对比重定向和类型，redirect_uri可能是多个
	if !s.grantTypeContrast(redirectUri, redirectUriReq) && !s.grantTypeContrast(grantTypes, grantTypeReq) {
		return "", "", "", "", errors.OtherError("type or redirect_uri is error")
	}

	// 1.2.1 查询scope
	scopeList := make([]models.OAuth2Scope, len(scopeReq))
	for i, srv := range scopeReq {
		id, name, tags, permission, remarks, err := s.db.SelScopeTag(srv)
		if err != nil {
			return "", "", "", redirectUriReq, errors.InvalidRequestError
		}
		scopeList[i].Id = id
		scopeList[i].Name = name
		scopeList[i].Tag = tags
		scopeList[i].Permission = permission
		scopeList[i].Remarks = remarks
	}

	// 1.2.1.0 判断该client是否有这个scope
	err = s.validateScope(scopeList, scopeReq)
	if err != nil {
		return "", "", "", "", errors.OtherError("scope不存在")
	}

	// 1.2.1.1 查询用户是否授权给该client的scope
	scopeTag, _ := s.db.SelUserScopeTag(username)
	scopeUserTag := tool.StringListDestroy(scopeTag)

	err = s.validateScope(scopeList, scopeUserTag)
	if err != nil {
		return "", "", "", "", err
	}

	// 1.2.2 记录在att里的scope
	scopeJsonStr, err := json.Marshal(scopeList)
	if err != nil {
		return "", "", "", redirectUriReq, errors.InvalidRequestError
	}

	// 2.创建授权码
	state = tool.CreateAuthorizeState()

	// 2.1将授权信息格式一下
	att := &models.Attribute{
		Username:               username,
		ClientId:               clientIdReq,
		AuthorizationGrantType: grantTypeReq,
		RedirectUri:            redirectUriReq,
		Scopes:                 string(scopeJsonStr),
		CodeExpireTime:         codeExp,
	}
	attribute := att.Serialize()

	// 2.2.存储一次授权信息，在这里存储codeChallenge，存储的clientId为client的主键，而非client_id
	err = s.db.SaveAuthorization(registerId, username, grantTypeReq, attribute, state, codeChallenge, codeChallengeMethod)
	if err != nil {
		return "", "", "", "", errors.OtherError("授权信息存储失败，可能会导致授权失败")
	}

	// 3.启动consent，查询scope的tag，查询到该scope-tag，范围符合，就直接调用Authentication，没有查到就不做处理
	scopeUser, _ := s.db.SelConsentInfo(registerId, username)
	if len(scopeUser) > 0 {
		scopeUserList := tool.StringListDestroy(scopeUser)
		var isHave []string
		for _, sl := range scopeUserList {
			for _, sr := range scopeList {
				if sl == sr.Tag {
					isHave = append(isHave, sl)
				}
			}
		}

		code, rUri, _ := s.Authentication(state, username, isHave)
		// 则直接返回code，uri
		return "", code, "", rUri, nil
	}

	// 返回信息
	return clientId, state, string(scopeJsonStr), redirectUriReq, nil
}

// Authentication 认证
func (s *oauthServiceImpl) Authentication(stateReq, usernameReq string, scopeReq []string) (code, rUri string, err error) {
	// 2.先查询认证信息，需要三个一起查，如果state查不到就直接返回错误
	aId, attInfo, err := s.db.SelAuthorizationInfo(stateReq, usernameReq)

	att := &models.Attribute{}
	err = att.Deserialize(attInfo)
	if err != nil {
		return "", "", errors.OtherError("authentication info is error")
	}
	rUri = att.RedirectUri

	// 2.1.0 匹配scope
	var scopeListAtt []models.OAuth2Scope
	err = json.Unmarshal([]byte(att.Scopes), &scopeListAtt)

	err = s.validateScope(scopeListAtt, scopeReq)
	if err != nil {
		return "", "", errors.OtherError("scope is error")
	}

	// 2.1.两个错误集中处理，判断是否查询到了，判断用户，clientId，范围，是否正确，防止被更改
	if err != nil || usernameReq != att.Username {
		return "", rUri, errors.OtherError("authentication is error")
	}

	// 生成code
	code = tool.CreateAuthorizeCode()

	// 3.如果上面都通过了，代表信息有效，就保存一下code
	err = s.db.SaveAuthentication(aId, code, tool.StringListBuild(scopeReq), att.CodeExpireTime)

	// 4.保存一下consent，用于判断下次授权直接获取code
	var tmpScope []string
	for _, v := range scopeListAtt {
		tmpScope = append(tmpScope, v.Tag)
	}
	err = s.db.SaveConsent(aId, usernameReq, tool.StringListBuild(tmpScope))
	if err != nil {
		return "", "", errors.OtherError("authentication is error")
	}

	return
}

// Token 生成token
func (s *oauthServiceImpl) Token(codeReq, clientIdReq, redirectUriReq, clientSecretReq, codeVerifierReq, requestUrl string) (rt models.ResultToken, err error) {
	// 1.查询认证的数据,验证code是否过期
	aId, userName, att, codeChallengeCode, scope, codeExpireTime, err := s.db.SelAuthentication(codeReq)
	if err != nil || !tool.IsExpired(codeExpireTime) {
		// 返回ResultToken本身结构体，防止变量逃逸。rt是空的结构体
		return rt, errors.OtherError("code is expires")
	}

	// 1.2.验证重要的client...上次写完了
	err = s.validateClient(att, clientIdReq)
	if err != nil {
		return rt, err
	}

	// 1.1.查询认证的scope权限
	permissionStr, err := s.getScopePermission(scope)
	if err != nil {
		return rt, err
	}

	// 2.查询client的密钥，重定向uri，token过期时间
	_, _, _, clientSecret, grantTDb, uriDB, _, _, tokenExpireTime, refreshTokenExpireTime, err := s.db.SelClient(clientIdReq)
	if err != nil {
		return rt, errors.OtherError("index error")
	}

	// 1.2.验证重定向url，OAuth2.1标准，可能有多个
	if !strings.Contains(uriDB, redirectUriReq) {
		return rt, errors.OtherError("redirect_uri info is error")
	}

	// 2.密钥验证
	var resultOk = true // 给验证结果默认值
	// 2.1.pk_ce，模式

	if len(codeVerifierReq) > 0 && len(clientSecretReq) == 0 {
		resultOk = tool.VerifyPKCESHA256(codeChallengeCode, codeVerifierReq)
		// 2.2.如果是pk ce模式，又带了secret，就一起验证
	} else if len(codeVerifierReq) > 0 && len(clientSecretReq) > 0 {
		err = bcrypt.CompareHashAndPassword([]byte(clientSecret), []byte(clientSecretReq))
		resultOk = tool.VerifyPKCESHA256(codeChallengeCode, codeVerifierReq)
		// 2.3.最后就只有secret，做单验证
	} else {
		err = bcrypt.CompareHashAndPassword([]byte(clientSecret), []byte(clientSecretReq))
	}
	if !resultOk && err != nil {
		return rt, errors.OtherError("key is error")
	}

	// 3.生成token,openId,token过期时间
	_, tExp := tool.CalculateExpiresTime(tokenExpireTime)
	var rExp int64
	var refreshToken string

	// 3.1.是否需要刷新token
	if s.grantTypeContrast(grantTDb, refreshGrantType) {
		refreshToken = tool.CreateRefreshToken()
		_, rExp = tool.CalculateExpiresTime(refreshTokenExpireTime)
	}

	rt = models.ResultToken{
		Token:        tool.CreateToken(clientIdReq, userName, requestUrl, permissionStr, tExp),
		RefreshToken: refreshToken,
		Scope:        scope,
		TokenType:    store.AccessTokenType,
		ExpiresIn:    tokenExpireTime,
		OpenId:       tool.CreateOpenId(),
	}

	// 4.保存token，和刷新token的信息，和openid的信息
	err = s.db.SaveToken(aId, rt.Token, rt.RefreshToken, rt.OpenId, scope, tExp, rExp)
	if err != nil {
		return rt, errors.OtherError("token save error : " + err.Error())
	}
	return
}

// ClientCredentials 客户端模式
func (s *oauthServiceImpl) ClientCredentials(gantType, clientId, clientSecret, requestUrl string) (rt models.ResultToken, err error) {
	// 1.查询客户端信息,验证模式，只有这个需要验证，其它的参数对的上
	_, clientId, clientName, secret, grantTypeDb, _, scopeDb, _, tokenExpireTime, _, err := s.db.SelClient(clientId)

	if err != nil || !s.grantTypeContrast(grantTypeDb, gantType) {
		return rt, errors.OtherError("The index is not supported")
	}

	// 1.1 查询scope
	permissionStr, err := s.getScopePermission(scopeDb)
	if err != nil {
		return rt, err
	}

	// 2.比对secret
	err = bcrypt.CompareHashAndPassword([]byte(secret), []byte(clientSecret))
	if err != nil {
		return rt, errors.OtherError("index secret is error")
	}

	// 3.客户端模式，id=uuid,clientId=clientId,clientName=username,accessToken,tokenExp，默认保存所有scope
	_, tExp := tool.CalculateExpiresTime(tokenExpireTime)

	rt = models.ResultToken{
		Token:     tool.CreateToken(clientId, clientName, requestUrl, permissionStr, tExp),
		Scope:     scopeDb,
		TokenType: store.AccessTokenType,
		ExpiresIn: tExp,
	}

	// 3.1.保存一下授权信息
	err = s.db.SaveAuthorization(clientId, clientName, gantType, "", "", "", "")
	if err != nil {
		return rt, errors.OtherError("save authorization info error")
	}
	// 3.2.保存一下token
	err = s.db.SaveToken(uuid.New().String(), rt.Token, "", "", scopeDb, tExp, 0)
	// 3.3.如果保存出现问题
	if err != nil {
		return rt, errors.OtherError("save token info error")
	}
	return
}

// RefreshToken 刷新token
func (s *oauthServiceImpl) RefreshToken(clientIdReq, clientSecret, refreshToken, requestUrl string) (rt models.ResultToken, err error) {
	// 1.查询刷新token的信息
	aId, att, username, scope, openId, refreshTokenExpireTime, err := s.db.SelRefreshToken(refreshToken)
	if err != nil {
		return rt, errors.OtherError("refresh token is error")
	}

	// 1.2.验证client是否对应这个token
	err = s.validateClient(att, clientIdReq)
	if err != nil {
		return rt, err
	}

	// 2.查询client信息
	_, _, _, secret, grantTypes, _, _, _, tokenExp, refreshTokenExp, err := s.db.SelClient(clientIdReq)
	if err != nil {
		return rt, errors.OtherError("index is error")
	}

	// 3.1.对比是否支持刷新token，有可能客户端取消刷新token，则刷新token失效
	if !s.grantTypeContrast(grantTypes, refreshGrantType) {
		return rt, errors.OtherError("index not support mode")
	}

	// 3.对比密钥
	err = bcrypt.CompareHashAndPassword([]byte(secret), []byte(clientSecret))

	// 4.刷新token时间
	if err != nil || !tool.IsExpired(refreshTokenExpireTime) {
		return rt, errors.OtherError("refresh token info is error")
	}

	// 5.如果信息有效，就重新生成token，openId不重新生成
	_, tExp := tool.CalculateExpiresTime(tokenExp)
	_, rExp := tool.CalculateExpiresTime(refreshTokenExp)

	// 6.获取scope信息
	permission, err := s.getScopePermission(scope)
	if err != nil {
		return rt, err
	}

	rt = models.ResultToken{
		Token:        tool.CreateToken(clientIdReq, username, requestUrl, permission, tExp),
		RefreshToken: tool.CreateRefreshToken(), // 如果客户端支持了该模式，则必须有刷新token
		Scope:        scope,
		TokenType:    store.AccessTokenType,
		ExpiresIn:    tExp,
		OpenId:       openId,
	}

	// 6.保存一下新的token
	err = s.db.SaveToken(aId, rt.Token, rt.RefreshToken, rt.OpenId, scope, tExp, rExp)
	if err != nil {
		return rt, errors.OtherError("save token info error")
	}
	return
}

// RevokeToken 注销token，删除数据库中的token，但token依旧会有效，只能删除刷新token，把token过期时间设置短一些
func (s *oauthServiceImpl) RevokeToken(clientId, clientSecret, token string) (bool, error) {
	// 1.查询token，是否存在
	aId, clientIdDb, _, _, err := s.db.SelToken(token)
	if err != nil {
		return false, errors.OtherError("查询token失败")
	}

	// 2.查询token的表里，client是否是传过来的client
	_, _, _, secret, _, _, _, _, _, _, err := s.db.SelClient(clientId)
	if err != nil {
		return false, errors.OtherError("注销失败，客户端不是该客户端")
	}

	// 3.数据库查询是否错误，验证客户端
	if err != nil || clientId != clientIdDb && bcrypt.CompareHashAndPassword([]byte(secret), []byte(clientSecret)) != nil {
		return false, errors.OtherError("客户端信息异常")
	}
	// 4.根据token和刷新token，删除整条授权信息
	err = s.db.DelAuthentication(aId)
	if err != nil {
		return false, errors.OtherError("注销失败")
	}
	return true, nil
}

/**
oauthService的一些方法，不包含在接口里
*/

// 匹配grant_type
func (s *oauthServiceImpl) grantTypeContrast(grantTypeDb, grantType string) bool {
	return strings.Contains(grantTypeDb, grantType)
}

// 获取scope的权限
func (s *oauthServiceImpl) getScopePermission(scope string) (permission []string, err error) {
	scopeListTag := tool.StringListDestroy(scope)
	var permissionStr []string
	for _, v := range scopeListTag {
		_, _, _, permission, _, err := s.db.SelScopeTag(v)
		if err != nil {
			return nil, errors.OtherError("scope is error")
		}
		permissionStr = append(permissionStr, permission)
	}
	return permissionStr, nil
}

// 验证scope
func (s *oauthServiceImpl) validateScope(scopeList []models.OAuth2Scope, scopeTag []string) error {
	found := false
	for _, attSl := range scopeList {
		for _, scq := range scopeTag {
			if attSl.Tag == scq {
				found = true
				break
			}
		}
		if !found {
			return errors.OtherError("用户未授权，请联系管理员")
		}
		found = false
	}
	return nil
}

// 验证client和申请的client是否一致
func (s *oauthServiceImpl) validateClient(att, clientReq string) (err error) {
	attribute := &models.Attribute{}
	err = attribute.Deserialize(att)
	if err != nil || attribute.ClientId != clientReq {
		return errors.OtherError("client info is error")
	}
	return nil
}
